/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2006 Tim Angus

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */

#include "g_local.h"


/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience.  They are turned into normal brushes by the utilities.
 */

/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
 */
void SP_info_null(gentity_t *self) {
  G_FreeEntity(self);
}

/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for in-game calculation, like jumppad targets.
target_position does the same thing
 */
void SP_info_notnull(gentity_t *self) {
  G_SetOrigin(self, self->s.origin);
}

/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear
Non-displayed light.
"light" overrides the default 300 intensity.
Linear checbox gives linear falloff instead of inverse square
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
 */
void SP_light(gentity_t *self) {
  G_FreeEntity(self);
}

/*
=================================================================================

TELEPORTERS

=================================================================================
 */

void TeleportZombie(gentity_t *player, vec3_t origin, vec3_t angles) {
  // unlink to make sure it can't possibly interfere with G_KillBox
  
  
  //Check if there is space to move
  
  trap_UnlinkEntity(player);

  VectorCopy(origin, player->client->ps.origin);
  player->client->ps.origin[ 2 ] += 1;

  // spit the player out
  AngleVectors(angles, player->client->ps.velocity, NULL, NULL);
  VectorScale(player->client->ps.velocity, 400, player->client->ps.velocity);
  player->client->ps.pm_time = 160; // hold time
  player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;

  // toggle the teleport bit so the client knows to not lerp
  player->client->ps.eFlags ^= EF_TELEPORT_BIT;
  G_UnlaggedClear(player);

  // set angles
  G_SetClientViewAngle(player, angles);

  // kill anything at the destination
  if (player->client->sess.sessionTeam != TEAM_SPECTATOR)
    G_KillBox(player);

  // save results of pmove
  BG_PlayerStateToEntityState(&player->client->ps, &player->s, qtrue);

  // use the precise origin for linking
  VectorCopy(player->client->ps.origin, player->r.currentOrigin);

  if (player->client->sess.sessionTeam != TEAM_SPECTATOR)
    trap_LinkEntity(player);
}

void TeleportPlayer(gentity_t *player, vec3_t origin, vec3_t angles) {
  // unlink to make sure it can't possibly interfere with G_KillBox
  trap_UnlinkEntity(player);

  VectorCopy(origin, player->client->ps.origin);
  player->client->ps.origin[ 2 ] += 1;

  // spit the player out
  AngleVectors(angles, player->client->ps.velocity, NULL, NULL);
  //Not spitting here
  //VectorScale(player->client->ps.velocity, 400, player->client->ps.velocity);
  player->client->ps.pm_time = 160; // hold time
  player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;

  // toggle the teleport bit so the client knows to not lerp
  player->client->ps.eFlags ^= EF_TELEPORT_BIT;
  G_UnlaggedClear(player);

  // set angles
  G_SetClientViewAngle(player, angles);

  // kill anything at the destination
  if (player->client->sess.sessionTeam != TEAM_SPECTATOR)
    G_KillBox(player);

  // save results of pmove
  BG_PlayerStateToEntityState(&player->client->ps, &player->s, qtrue);

  // use the precise origin for linking
  VectorCopy(player->client->ps.origin, player->r.currentOrigin);

  if (player->client->sess.sessionTeam != TEAM_SPECTATOR)
    trap_LinkEntity(player);
}

/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
Point teleporters at these.
Now that we don't have teleport destination pads, this is just
an info_notnull
 */
void SP_misc_teleporter_dest(gentity_t *ent) {
}


//===========================================================

/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
"model"   arbitrary .md3 file to display
 */
void SP_misc_model(gentity_t *ent) {
#if 0
  ent->s.modelindex = G_ModelIndex(ent->model);
  VectorSet(ent->mins, -16, -16, -16);
  VectorSet(ent->maxs, 16, 16, 16);
  trap_LinkEntity(ent);

  G_SetOrigin(ent, ent->s.origin);
  VectorCopy(ent->s.angles, ent->s.apos.trBase);
#else
  G_FreeEntity(ent);
#endif
}

//===========================================================

void locateCamera(gentity_t *ent) {
  vec3_t dir;
  gentity_t *target;
  gentity_t *owner;

  owner = G_PickTarget(ent->target);
  if (!owner) {
    G_Printf("Couldn't find target for misc_portal_surface\n");
    G_FreeEntity(ent);
    return;
  }
  ent->r.ownerNum = owner->s.number;

  // frame holds the rotate speed
  if (owner->spawnflags & 1)
    ent->s.frame = 25;
  else if (owner->spawnflags & 2)
    ent->s.frame = 75;

  // swing camera ?
  if (owner->spawnflags & 4) {
    // set to 0 for no rotation at all
    ent->s.powerups = 0;
  } else
    ent->s.powerups = 1;

  // clientNum holds the rotate offset
  ent->s.clientNum = owner->s.clientNum;

  VectorCopy(owner->s.origin, ent->s.origin2);

  // see if the portal_camera has a target
  target = G_PickTarget(owner->target);
  if (target) {
    VectorSubtract(target->s.origin, owner->s.origin, dir);
    VectorNormalize(dir);
  } else
    G_SetMovedir(owner->s.angles, dir);

  ent->s.eventParm = DirToByte(dir);
}

/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
This must be within 64 world units of the surface!
 */
void SP_misc_portal_surface(gentity_t *ent) {
  VectorClear(ent->r.mins);
  VectorClear(ent->r.maxs);
  trap_LinkEntity(ent);

  ent->r.svFlags = SVF_PORTAL;
  ent->s.eType = ET_PORTAL;

  if (!ent->target) {
    VectorCopy(ent->s.origin, ent->s.origin2);
  } else {
    ent->think = locateCamera;
    ent->nextthink = level.time + 100;
  }
}

/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing

The target for a misc_portal_director.  You can set either angles or target another entity to determine the direction of view.
"roll" an angle modifier to orient the camera around the target vector;
 */
void SP_misc_portal_camera(gentity_t *ent) {
  float roll;

  VectorClear(ent->r.mins);
  VectorClear(ent->r.maxs);
  trap_LinkEntity(ent);

  G_SpawnFloat("roll", "0", &roll);

  ent->s.clientNum = roll / 360.0f * 256;
}

/*
======================================================================

  NEAT EFFECTS AND STUFF FOR TREMULOUS

======================================================================
 */

void SP_toggle_particle_system(gentity_t *self) {
  //toggle EF_NODRAW
  self->s.eFlags ^= EF_NODRAW;

  self->nextthink = 0;
}

/*
===============
SP_use_particle_system

Use function for particle_system
===============
 */
void SP_use_particle_system(gentity_t *self, gentity_t *other, gentity_t *activator) {
  SP_toggle_particle_system(self);

  if (self->wait > 0.0f) {
    self->think = SP_toggle_particle_system;
    self->nextthink = level.time + (int) (self->wait * 1000);
  }
}

/*
===============
SP_spawn_particle_system

Spawn function for particle system
===============
 */
void SP_misc_particle_system(gentity_t *self) {
  char *s;

  G_SetOrigin(self, self->s.origin);

  G_SpawnString("psName", "", &s);
  G_SpawnFloat("wait", "0", &self->wait);

  //add the particle system to the client precache list
  self->s.modelindex = G_ParticleSystemIndex(s);

  if (self->spawnflags & 1)
    self->s.eFlags |= EF_NODRAW;

  self->use = SP_use_particle_system;
  self->s.eType = ET_PARTICLE_SYSTEM;
  trap_LinkEntity(self);
}

/*
===============
SP_use_anim_model

Use function for anim model
===============
 */
void SP_use_anim_model(gentity_t *self, gentity_t *other, gentity_t *activator) {
  if (self->spawnflags & 1) {
    //if spawnflag 1 is set
    //toggle EF_NODRAW
    if (self->s.eFlags & EF_NODRAW)
      self->s.eFlags &= ~EF_NODRAW;
    else
      self->s.eFlags |= EF_NODRAW;
  } else {
    //if the animation loops then toggle the animation
    //toggle EF_MOVER_STOP
    if (self->s.eFlags & EF_MOVER_STOP)
      self->s.eFlags &= ~EF_MOVER_STOP;
    else
      self->s.eFlags |= EF_MOVER_STOP;
  }
}

/*
===============
SP_misc_anim_model

Spawn function for anim model
===============
 */
void SP_misc_anim_model(gentity_t *self) {
  self->s.powerups = (int) self->animation[ 0 ];
  self->s.weapon = (int) self->animation[ 1 ];
  self->s.torsoAnim = (int) self->animation[ 2 ];
  self->s.legsAnim = (int) self->animation[ 3 ];

  self->s.angles2[ 0 ] = self->pos2[ 0 ];

  //add the model to the client precache list
  self->s.modelindex = G_ModelIndex(self->model);

  self->use = SP_use_anim_model;

  self->s.eType = ET_ANIMMAPOBJ;

  // spawn with animation stopped
  if (self->spawnflags & 2)
    self->s.eFlags |= EF_MOVER_STOP;

  trap_LinkEntity(self);
}

/*
===============
SP_use_light_flare

Use function for light flare
===============
 */
void SP_use_light_flare(gentity_t *self, gentity_t *other, gentity_t *activator) {
  self->s.eFlags ^= EF_NODRAW;
}

/*
===============
findEmptySpot

Finds an empty spot radius units from origin
==============
 */
static void findEmptySpot(vec3_t origin, float radius, vec3_t spot) {
  int i, j, k;
  vec3_t delta, test, total;
  trace_t tr;

  VectorClear(total);

  //54(!) traces to test for empty spots
  for (i = -1; i <= 1; i++) {
    for (j = -1; j <= 1; j++) {
      for (k = -1; k <= 1; k++) {
        VectorSet(delta, (i * radius),
                (j * radius),
                (k * radius));

        VectorAdd(origin, delta, test);

        trap_Trace(&tr, test, NULL, NULL, test, -1, MASK_SOLID);

        if (!tr.allsolid) {
          trap_Trace(&tr, test, NULL, NULL, origin, -1, MASK_SOLID);
          VectorScale(delta, tr.fraction, delta);
          VectorAdd(total, delta, total);
        }
      }
    }
  }

  VectorNormalize(total);
  VectorScale(total, radius, total);
  VectorAdd(origin, total, spot);
}

/*
===============
SP_misc_light_flare

Spawn function for light flare
===============
 */
void SP_misc_light_flare(gentity_t *self) {
  self->s.eType = ET_LIGHTFLARE;
  self->s.modelindex = G_ShaderIndex(self->targetShaderName);
  VectorCopy(self->pos2, self->s.origin2);

  //try to find a spot near to the flare which is empty. This
  //is used to facilitate visibility testing
  findEmptySpot(self->s.origin, 8.0f, self->s.angles2);

  self->use = SP_use_light_flare;

  G_SpawnFloat("speed", "200", &self->speed);
  self->s.time = self->speed;

  G_SpawnInt("mindist", "0", &self->s.generic1);

  if (self->spawnflags & 1)
    self->s.eFlags |= EF_NODRAW;

  trap_LinkEntity(self);
}
